1use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::*;
6use anyhow::Result;
7use std::io::{Read, Seek, Write};
8
9#[derive(Debug)]
10pub struct QlieScriptBuilder {}
12
13impl QlieScriptBuilder {
14 pub fn new() -> Self {
16 Self {}
17 }
18}
19
20impl ScriptBuilder for QlieScriptBuilder {
21 fn default_encoding(&self) -> Encoding {
22 Encoding::Cp932
23 }
24
25 fn build_script(
26 &self,
27 buf: Vec<u8>,
28 _filename: &str,
29 encoding: Encoding,
30 _archive_encoding: Encoding,
31 config: &ExtraConfig,
32 _archive: Option<&Box<dyn Script>>,
33 ) -> Result<Box<dyn Script>> {
34 Ok(Box::new(QlieScript::new(
35 MemReader::new(buf),
36 encoding,
37 config,
38 )?))
39 }
40
41 fn extensions(&self) -> &'static [&'static str] {
42 &["s"]
43 }
44
45 fn script_type(&self) -> &'static ScriptType {
46 &ScriptType::Qlie
47 }
48
49 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
50 if is_this_format(buf, buf_len) {
51 Some(20)
52 } else {
53 None
54 }
55 }
56}
57
58pub fn is_this_format(buf: &[u8], buf_len: usize) -> bool {
60 if buf_len < 2 {
61 return false;
62 }
63 let mut reader = MemReaderRef::new(&buf[..buf_len]);
64 let mut parser = match QlieParser::new(&mut reader, Encoding::Utf8) {
65 Ok(p) => p,
66 Err(_) => return false,
67 };
68 loop {
69 let line = match parser.next_line() {
70 Ok(Some(l)) => l,
71 Ok(None) => break,
72 Err(_) => return false,
73 };
74 let line = line.trim();
75 let line = line.to_lowercase();
76 if line == "@@@avg\\header.s" && line == "@@@avg2\\header.s" {
77 return true;
78 }
79 }
80 return false;
81}
82
83#[derive(Debug, Clone)]
84enum TagData {
85 Simple(String),
86 KeyValue(String, String),
87}
88
89#[derive(Debug, Clone)]
90struct Tag {
91 name: String,
92 args: Vec<TagData>,
93}
94
95impl Tag {
96 fn from_str(s: &str) -> Result<Self> {
97 let mut current = String::new();
98 let mut name = None;
99 let mut arg_key = None;
100 let mut args = Vec::new();
101 let mut in_quote = false;
102 for c in s.chars() {
103 if !in_quote && c == ':' {
104 if name.is_none() {
105 return Err(anyhow::anyhow!("Invalid tag name: {}", s));
106 }
107 arg_key = Some(current.to_string());
108 current.clear();
109 continue;
110 }
111 if !in_quote && c == ',' {
112 if let Some(key) = arg_key.take() {
113 args.push(TagData::KeyValue(key, current.to_string()));
114 } else if !current.is_empty() {
115 if name.is_none() {
116 name = Some(current.to_string());
117 } else {
118 args.push(TagData::Simple(current.to_string()));
119 }
120 }
121 current.clear();
122 continue;
123 }
124 if c == '"' {
125 in_quote = !in_quote;
126 continue;
127 }
128 current.push(c);
129 }
130 if !current.is_empty() {
131 if let Some(key) = arg_key.take() {
132 args.push(TagData::KeyValue(key, current.to_string()));
133 } else {
134 if name.is_none() {
135 name = Some(current.to_string());
136 } else {
137 args.push(TagData::Simple(current.to_string()));
138 }
139 }
140 }
141 Ok(Self {
142 name: name.ok_or(anyhow::anyhow!("Invalid tag name"))?,
143 args,
144 })
145 }
146
147 fn dump(&self) -> String {
148 let mut parts = Vec::new();
149 parts.push(self.name.clone());
150 for arg in &self.args {
151 match arg {
152 TagData::Simple(s) => {
153 if s.contains(',') || s.contains(':') {
154 parts.push(format!("\"{}\"", s));
155 } else {
156 parts.push(s.clone());
157 }
158 }
159 TagData::KeyValue(k, v) => {
160 let v_str = if v.contains(',') || v.contains(':') {
161 format!("\"{}\"", v)
162 } else {
163 v.clone()
164 };
165 parts.push(format!("{}:{}", k, v_str));
166 }
167 }
168 }
169 parts.join(",")
170 }
171}
172
173#[derive(Debug, Clone)]
174enum QlieParsedLine {
175 Label(String),
177 Include(String),
179 LineTag(Tag),
181 Command(Tag),
183 Name(String),
185 Sound(String),
187 Text(String),
189 Empty,
191}
192
193struct QlieParser<T> {
194 reader: T,
195 encoding: Encoding,
196 bom: BomType,
197 parsed: Vec<QlieParsedLine>,
198 is_crlf: bool,
199}
200
201impl<T: Read + Seek> QlieParser<T> {
202 pub fn new(mut reader: T, mut encoding: Encoding) -> Result<Self> {
203 let mut bom = [0; 3];
204 let valid_len = reader.peek(&mut bom)?;
205 let bom = if valid_len >= 2 {
206 if bom[0] == 0xFF && bom[1] == 0xFE {
207 BomType::Utf16LE
208 } else if bom[0] == 0xFE && bom[1] == 0xFF {
209 BomType::Utf16BE
210 } else if valid_len >= 3 && bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF {
211 BomType::Utf8
212 } else {
213 BomType::None
214 }
215 } else {
216 BomType::None
217 };
218 match bom {
219 BomType::Utf16LE => {
220 encoding = Encoding::Utf16LE;
221 reader.seek_relative(2)?;
222 }
223 BomType::Utf16BE => {
224 encoding = Encoding::Utf16BE;
225 reader.seek_relative(2)?;
226 }
227 BomType::Utf8 => {
228 encoding = Encoding::Utf8;
229 reader.seek_relative(3)?;
230 }
231 BomType::None => {}
232 }
233 Ok(Self {
234 reader,
235 encoding,
236 bom,
237 parsed: Vec::new(),
238 is_crlf: false,
239 })
240 }
241
242 fn next_line(&mut self) -> Result<Option<String>> {
243 let mut sbuf = Vec::new();
244 let mut is_eof = false;
245 if self.encoding.is_utf16le() {
246 let mut buf = [0; 2];
247 loop {
248 let readed = self.reader.read(&mut buf)?;
249 if readed == 0 {
250 is_eof = true;
251 break;
252 }
253 if buf == [0x0A, 0x00] {
254 break;
255 }
256 sbuf.extend_from_slice(&buf);
257 }
258 } else if self.encoding.is_utf16be() {
259 let mut buf = [0; 2];
260 loop {
261 let readed = self.reader.read(&mut buf)?;
262 if readed == 0 {
263 is_eof = true;
264 break;
265 }
266 if buf == [0x00, 0x0A] {
267 break;
268 }
269 sbuf.extend_from_slice(&buf);
270 }
271 } else {
272 let mut buf = [0; 1];
273 loop {
274 let readed = self.reader.read(&mut buf)?;
275 if readed == 0 {
276 is_eof = true;
277 break;
278 }
279 if buf[0] == 0x0A {
280 break;
281 }
282 sbuf.push(buf[0]);
283 }
284 }
285 if sbuf.is_empty() {
286 return Ok(if is_eof { None } else { Some(String::new()) });
287 }
288 let mut s = decode_to_string(self.encoding, &sbuf, true)?;
289 if s.ends_with("\r") {
290 s.pop();
291 self.is_crlf = true;
292 }
293 Ok(Some(s))
294 }
295
296 pub fn parse(&mut self) -> Result<()> {
297 while let Some(line) = self.next_line()? {
298 let line = line.trim();
299 if line.is_empty() {
300 self.parsed.push(QlieParsedLine::Empty);
301 } else if line.starts_with("@@@") {
302 self.parsed
303 .push(QlieParsedLine::Include(line[3..].to_string()));
304 } else if line.starts_with("@@") {
305 self.parsed
306 .push(QlieParsedLine::Label(line[2..].to_string()));
307 } else if line.starts_with("^") {
308 let tag = Tag::from_str(&line[1..])?;
309 self.parsed.push(QlieParsedLine::LineTag(tag));
310 } else if line.starts_with("\\") {
311 let tag = Tag::from_str(&line[1..])?;
312 self.parsed.push(QlieParsedLine::Command(tag));
313 } else if line.starts_with("【") && line.ends_with("】") {
314 let name = line[3..line.len() - 3].to_string();
315 self.parsed.push(QlieParsedLine::Name(name));
316 } else if line.starts_with("%") {
317 let sound = line[3..].to_string();
318 self.parsed.push(QlieParsedLine::Sound(sound));
319 } else {
320 self.parsed.push(QlieParsedLine::Text(line.to_string()));
321 }
322 }
323 Ok(())
324 }
325}
326
327#[derive(Debug)]
328struct QlieDumper<T: Write> {
329 writer: T,
330 encoding: Encoding,
331 is_crlf: bool,
332}
333
334impl<T: Write> QlieDumper<T> {
335 pub fn new(mut writer: T, bom: BomType, mut encoding: Encoding, is_crlf: bool) -> Result<Self> {
336 match bom {
337 BomType::Utf16LE => {
338 encoding = Encoding::Utf16LE;
339 }
340 BomType::Utf16BE => {
341 encoding = Encoding::Utf16BE;
342 }
343 BomType::Utf8 => {
344 encoding = Encoding::Utf8;
345 }
346 BomType::None => {}
347 }
348 writer.write_all(bom.as_bytes())?;
349 Ok(Self {
350 writer,
351 encoding,
352 is_crlf,
353 })
354 }
355
356 fn write_line(&mut self, line: &str) -> Result<()> {
357 let line = if self.is_crlf {
358 format!("{}\r\n", line)
359 } else {
360 format!("{}\n", line)
361 };
362 let data = encode_string(self.encoding, &line, false)?;
363 self.writer.write_all(&data)?;
364 Ok(())
365 }
366
367 pub fn dump(mut self, data: &[QlieParsedLine]) -> Result<()> {
368 for line in data {
369 match line {
370 QlieParsedLine::Label(s) => {
371 self.write_line(&format!("@@{}", s))?;
372 }
373 QlieParsedLine::Include(s) => {
374 self.write_line(&format!("@@@{}", s))?;
375 }
376 QlieParsedLine::LineTag(tag) => {
377 self.write_line(&format!("^{}", tag.dump()))?;
378 }
379 QlieParsedLine::Command(cmd) => {
380 self.write_line(&format!("\\{}", cmd.dump()))?;
381 }
382 QlieParsedLine::Name(name) => {
383 self.write_line(&format!("【{}】", name))?;
384 }
385 QlieParsedLine::Sound(sound) => {
386 self.write_line(&format!("%{}", sound))?;
387 }
388 QlieParsedLine::Text(text) => {
389 self.write_line(text)?;
390 }
391 QlieParsedLine::Empty => {
392 self.write_line("")?;
393 }
394 }
395 }
396 Ok(())
397 }
398}
399
400#[derive(Debug)]
401pub struct QlieScript {
402 bom: BomType,
403 parsed: Vec<QlieParsedLine>,
404 is_crlf: bool,
405}
406
407impl QlieScript {
408 pub fn new<T: Read + Seek>(data: T, encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
410 let mut parser = QlieParser::new(data, encoding)?;
411 parser.parse()?;
412 Ok(Self {
413 bom: parser.bom,
414 parsed: parser.parsed,
415 is_crlf: parser.is_crlf,
416 })
417 }
418}
419
420impl Script for QlieScript {
421 fn default_output_script_type(&self) -> OutputScriptType {
422 OutputScriptType::Json
423 }
424
425 fn default_format_type(&self) -> FormatOptions {
426 FormatOptions::None
427 }
428
429 fn extract_messages(&self) -> Result<Vec<Message>> {
430 let mut messages = Vec::new();
431 let mut name = None;
432 for line in &self.parsed {
433 match line {
434 QlieParsedLine::Name(n) => {
435 name = Some(n.to_string());
436 }
437 QlieParsedLine::Text(text) => {
438 messages.push(Message::new(text.replace("[n]", "\n"), name.take()));
439 }
440 QlieParsedLine::LineTag(tag) => {
441 if tag.name.to_lowercase() == "select" {
442 for arg in &tag.args {
443 match arg {
444 TagData::Simple(s) => {
445 messages.push(Message::new(s.clone(), None));
446 }
447 _ => {
448 return Err(anyhow::anyhow!(
449 "Invalid select tag argument: {:?}.",
450 tag
451 ));
452 }
453 }
454 }
455 } else if tag.name.to_lowercase() == "savetext" {
456 if tag.args.len() >= 1 {
457 match &tag.args[0] {
458 TagData::Simple(s) => {
459 messages.push(Message::new(s.clone(), None));
460 }
461 _ => {
462 return Err(anyhow::anyhow!(
463 "Invalid savetext tag argument: {:?}.",
464 tag
465 ));
466 }
467 }
468 }
469 }
470 }
471 _ => {}
472 }
473 }
474 Ok(messages)
475 }
476
477 fn import_messages<'a>(
478 &'a self,
479 messages: Vec<Message>,
480 file: Box<dyn WriteSeek + 'a>,
481 _filename: &str,
482 encoding: Encoding,
483 replacement: Option<&'a ReplacementTable>,
484 ) -> Result<()> {
485 let mut mess = messages.iter();
486 let mut mes = mess.next();
487 let mut lines = self.parsed.clone();
488 let mut name_index = None;
489 let mut index = 0;
490 let line_len = lines.len();
491 while index < line_len {
492 let line = lines[index].clone();
493 match line {
494 QlieParsedLine::Name(_) => {
495 name_index = Some(index);
496 }
497 QlieParsedLine::LineTag(tag) => {
498 if tag.name.to_lowercase() == "select" {
499 let mut new_tag = Tag {
500 name: tag.name.clone(),
501 args: Vec::new(),
502 };
503 for _ in &tag.args {
504 let mut message = match mes {
505 Some(m) => m.message.clone(),
506 None => {
507 return Err(anyhow::anyhow!("Not enough messages to import."));
508 }
509 };
510 mes = mess.next();
511 if let Some(repl) = replacement {
512 for (k, v) in &repl.map {
513 message = message.replace(k, v);
514 }
515 }
516 new_tag.args.push(TagData::Simple(message));
517 }
518 lines[index] = QlieParsedLine::LineTag(new_tag);
519 } else if tag.name.to_lowercase() == "savetext" {
520 if tag.args.len() >= 1 {
521 let mut message = match mes {
522 Some(m) => m.message.clone(),
523 None => {
524 return Err(anyhow::anyhow!("Not enough messages to import."));
525 }
526 };
527 mes = mess.next();
528 if let Some(repl) = replacement {
529 for (k, v) in &repl.map {
530 message = message.replace(k, v);
531 }
532 }
533 let new_tag = Tag {
534 name: tag.name.clone(),
535 args: vec![TagData::Simple(message)],
536 };
537 lines[index] = QlieParsedLine::LineTag(new_tag);
538 }
539 }
540 }
541 QlieParsedLine::Text(_) => {
542 if let Some(name_index) = name_index.take() {
543 let mut name = match mes {
544 Some(m) => match &m.name {
545 Some(n) => n.clone(),
546 None => return Err(anyhow::anyhow!("Expected name for message.")),
547 },
548 None => return Err(anyhow::anyhow!("Not enough messages to import.")),
549 };
550 if let Some(repl) = replacement {
551 for (k, v) in &repl.map {
552 name = name.replace(k, v);
553 }
554 }
555 lines[name_index] = QlieParsedLine::Name(name);
556 }
557 let mut message = match mes {
558 Some(m) => m.message.clone(),
559 None => return Err(anyhow::anyhow!("Not enough messages to import.")),
560 };
561 mes = mess.next();
562 if let Some(repl) = replacement {
563 for (k, v) in &repl.map {
564 message = message.replace(k, v);
565 }
566 }
567 lines[index] = QlieParsedLine::Text(message.replace("\n", "[n]"));
568 }
569 _ => {}
570 }
571 index += 1;
572 }
573 let dumper = QlieDumper::new(file, self.bom, encoding, self.is_crlf)?;
574 dumper.dump(&lines)?;
575 Ok(())
576 }
577}
578
579#[test]
580fn test_tag() {
581 let s = "tag1,\"test:a,c\",best:\"va,2:3\"";
582 let parts = Tag::from_str(s).unwrap();
583 assert_eq!(parts.name, "tag1");
584 assert_eq!(parts.args.len(), 2);
585 match &parts.args[0] {
586 TagData::Simple(v) => assert_eq!(v, "test:a,c"),
587 _ => panic!("Expected Simple"),
588 }
589 match &parts.args[1] {
590 TagData::KeyValue(k, v) => {
591 assert_eq!(k, "best");
592 assert_eq!(v, "va,2:3");
593 }
594 _ => panic!("Expected KeyValue"),
595 }
596 let dumped = parts.dump();
597 assert_eq!(dumped, s);
598}